﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using TrackIT.Common.Providers;
using Microsoft.Practices.Unity;

namespace TrackIT.Common.Repositories
{
    public class WorkLogRepository : IWorkLogRepository
    {
        private readonly IList<WorkEntry> _workItems;
        private WorkEntry _currentItem;
        private readonly IFileAccessRepository _fileAccessRepository;
        private readonly IDateTimeProvider _dateTimeProvider;
        private readonly IFileChangeNotifyProvider _fileChangeNotifyProvider;
        private readonly string _taskPrefix;
        private readonly string _workFilename;
        private readonly bool _detectWorkFileChange;

        public WorkLogRepository(IFileAccessRepository fileAccessRepository, IDateTimeProvider dateTimeProvider, IFileChangeNotifyProvider fileChangeNotifyProvider,
            [Dependency("TaskPrefix")] string taskPrefix, [Dependency("WorkFilename")] string workFilename, [Dependency("DetectWorkFileChange")] bool detectWorkFileChange)
        {
            _fileAccessRepository = fileAccessRepository;
            _dateTimeProvider = dateTimeProvider;
            _fileChangeNotifyProvider = fileChangeNotifyProvider;
            _taskPrefix = taskPrefix;
            _workFilename = workFilename;
            _detectWorkFileChange = detectWorkFileChange;

            _workItems = new List<WorkEntry>();
            _currentItem = null;

            if (_detectWorkFileChange)
                _fileChangeNotifyProvider.WorkfileChanged += WorkfileChanged;
        }

        public WorkEntry Current
        {
            get { return _currentItem; }
        }

        public void WorkfileChanged()
        {
            try
            {
                _fileChangeNotifyProvider.EnableChangeNotify = false;
                Load();
            }
            finally
            {
                _fileChangeNotifyProvider.EnableChangeNotify = true;
            }
        }

        public void Load()
        {
            _workItems.Clear();

            if (_fileAccessRepository != null)
            {
                List<string> lines = _fileAccessRepository.Load(_workFilename)
                        .Select(l => l.Replace(_taskPrefix, "")).ToList();

                if (lines.Any())
                {
                    WorkEntry firstWorkEntry = new WorkEntry(lines.First());
                    if (firstWorkEntry.EntryType != WorkEntryType.Date)
                        throw new ArgumentException("Worklog does not begin with a date marker");
                }
                DateTime lastDate = DateTime.MinValue;

                foreach (string l in lines)
                {
                    WorkEntry w = new WorkEntry(l);
                    if (w.EntryType == WorkEntryType.Date && w.Start.HasValue)
                    {
                        lastDate = w.Start.Value;
                    }
                    else
                    {
                        if (w.Start.HasValue)
                        {
                            w.Start = new DateTime(lastDate.Year, lastDate.Month, lastDate.Day, w.Start.Value.Hour, w.Start.Value.Minute, 0);
                        }
                        if (w.Stop.HasValue)
                        {
                            w.Stop = new DateTime(lastDate.Year, lastDate.Month, lastDate.Day, w.Stop.Value.Hour, w.Stop.Value.Minute, 0);
                        }
                    }
                    _workItems.Add(w);
                }

                _currentItem = _workItems.LastOrDefault(w => w.EntryType != WorkEntryType.Date);
            }
        }

        public void Save()
        {
            if (_fileAccessRepository != null)
            {
                List<string> entries = new List<string>();
                foreach (WorkEntry entry in _workItems)
                {
                    string entryLine = "";
                    switch (entry.EntryType)
                    {
                        case WorkEntryType.Date:
                            entryLine = string.Format("% {0:yyyy-MM-dd}", entry.Start);
                            break;

                        case WorkEntryType.Start:
                            entryLine = string.Format("% {0}{1} {2:HH:mm}", entry.IsMantisTicket ? _taskPrefix : "", entry.Task, entry.Start);
                            break;

                        case WorkEntryType.Stop:
                            entryLine = string.Format("% {0}{1} {2:HH:mm} {3:HH:mm}", entry.IsMantisTicket ? _taskPrefix : "", entry.Task, entry.Start, entry.Stop);
                            break;
                    }
                    entries.Add(entryLine);
                }
                _fileAccessRepository.Save(entries, _workFilename);
            }
        }

        public WorkEntry Start(string task)
        {
            if (_currentItem != null && _currentItem.EntryType == WorkEntryType.Start)
                throw new InvalidOperationException("Cannot start another task while one is already active");

            _currentItem = new WorkEntry(task, _dateTimeProvider.Now, null);

            DateTime now = _dateTimeProvider.Now;
            WorkEntry lastEntry = _workItems.LastOrDefault();

            if (lastEntry != null && lastEntry.Start.HasValue && lastEntry.Start.Value < Utils.Util.RemoveUnecessaryTimeParts(now))
            {
                WorkEntry dateMarker = new WorkEntry
                {
                    Task = null,
                    Start = new DateTime(now.Year, now.Month, now.Day),
                    Stop = null
                };
                _workItems.Add(dateMarker);
            }

            _workItems.Add(_currentItem);

            return _currentItem;
        }

        public WorkEntry Stop()
        {
            if (_currentItem == null || _currentItem.EntryType != WorkEntryType.Start)
                throw new InvalidOperationException("Cannot stop task if there is no active task");

            _currentItem.Stop = _dateTimeProvider.Now;

            _workItems.RemoveAt(_workItems.IndexOf(_currentItem));
            _workItems.Add(_currentItem);

            return _currentItem;
        }

        public WorkEntry GetLast()
        {
            return _currentItem;
        }

        public IEnumerable<Task> Tasks
        {
            get
            {
                var recentTaskListQuery =
                    (from w in _workItems
                     where !string.IsNullOrEmpty(w.Task)
                     select new { TaskName = w.Task }
                     ).Distinct();

                var recentDistinctTasksQuery =
                    (from t in recentTaskListQuery
                     select new Task(t.TaskName, t.TaskName)
                    );

                return recentDistinctTasksQuery.AsEnumerable();
            }
        }

        public Task GetTaskByName(string taskName)
        {
            return Tasks.SingleOrDefault(t => t.Name.Equals(taskName));
        }

        public IEnumerable<WorkEntry> WorkItems
        {
            get
            {
                return _workItems;
            }
        }

        public WorkEntryType GetNextActionType()
        {
            WorkEntryType returnAction = WorkEntryType.Start;

            if (_currentItem == null || _currentItem.EntryType == WorkEntryType.Date || _currentItem.EntryType == WorkEntryType.Stop)
            {
                returnAction = WorkEntryType.Start;
            }
            else if (_currentItem.EntryType == WorkEntryType.Start)
            {
                returnAction = WorkEntryType.Stop;
            }

            return returnAction;
        }

        public TimeSpan GetTimeSpentOnTask(string taskName)
        {
            var taskEntriesQuery =
                (from w in _workItems
                 where w.Task == taskName &&
                     // only valid completed entries
                 w.Start.HasValue && w.Stop.HasValue
                 group w by w.Task into s
                 select new
                 {
                     Duration = new TimeSpan(s.Sum(x => x.Start != null ? (x.Stop != null ? (x.Stop.Value - x.Start.Value).Ticks : 0) : 0))
                 }).ToList();

            if (!taskEntriesQuery.Any())
                return new TimeSpan(0);

            return taskEntriesQuery.Single().Duration;
        }

        public List<string> GetWorkSummary(FilterDates filterDates)
        {
            var summaryQuery =
                from w in _workItems
                where
                    // only valid completed entries
                    w.Start.HasValue && w.Stop.HasValue &&
                    // use filter criteria
                    w.Start.Value >= filterDates.StartDate &&
                    w.Stop.Value <= filterDates.StopDate
                group w by w.Task into s
                orderby s.Key
                select new
                {
                    TaskName = s.Key,
                    Duration = Math.Round(s.Sum(x => x.Start != null ? (x.Stop != null ? (x.Stop.Value - x.Start.Value).TotalMinutes : 0) : 0) / 60, 2)
                };

            List<string> summary = summaryQuery.Select(x => string.Format("{0} {1:0.00}", x.TaskName, x.Duration)).ToList();
            // add header
            summary.Insert(0, "EOL issues allocations");
            return summary;
        }

        public string GetTemporaryFolderName(ReportType reportType)
        {
            string reportName = GetReportName(reportType);
            string filename = string.Format("{0}.txt", reportName);
            return Path.Combine(Path.GetTempPath(), filename);
        }

        public string GetReportName(ReportType reportType)
        {
            string formatString = string.Empty;
            DateTime now = _dateTimeProvider.Now.Date;
            switch (reportType)
            {
                case ReportType.Monthly:
                    formatString = "{0:yyyy-MM} Summary";
                    break;

                case ReportType.Weekly:
                    if (now.DayOfWeek != DayOfWeek.Friday)
                    {
                        // get last weeks report
                        now = now.AddDays(-7);
                    }
                    int weekNumber = _dateTimeProvider.GetWeekNumberOfMonth(now);
                    formatString = "{0:yyyy-MM} Wk" + weekNumber + " Summary";
                    break;
            }

            string filename = string.Format(formatString, now);
            return filename;
        }

        public FilterDates ResolveDatesFromReportType(ReportType reportType)
        {
            CultureInfo defaultCultureInfo = CultureInfo.CurrentCulture;
            FilterDates filterDates = new FilterDates();
            DateTime now = _dateTimeProvider.Now;
            if (reportType == ReportType.Monthly)
            {
                if (now.Day <= 21)
                {
                    // get last month
                    filterDates.StartDate = new DateTime(now.Year, now.Month - 1, 1);
                    filterDates.StopDate = new DateTime(now.Year, now.Month, 1).AddDays(-1);
                }
                else
                {
                    // get current month
                    filterDates.StartDate = new DateTime(now.Year, now.Month, 1);
                    filterDates.StopDate = new DateTime(now.Year, now.Month + 1, 1).AddDays(-1);
                }
            }
            if (reportType == ReportType.Weekly)
            {
                DayOfWeek firstDay = defaultCultureInfo.DateTimeFormat.FirstDayOfWeek;
                DateTime firstDayInWeek = now;
                while (firstDayInWeek.DayOfWeek != firstDay)
                    firstDayInWeek = firstDayInWeek.AddDays(-1);

                filterDates.StartDate = firstDayInWeek;
                filterDates.StopDate = firstDayInWeek.AddDays(+6);

                // Move back a week if not ran on a Friday
                if (now.DayOfWeek < DayOfWeek.Friday)
                {
                    filterDates.StartDate = filterDates.StartDate.AddDays(-7);
                    filterDates.StopDate = filterDates.StopDate.AddDays(-7);
                }
            }

            return filterDates;
        }
    }
}